(function (global, factory) {
    "use strict";
    if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = global.document ?
            factory(global, true) :
            function (w) {
                if (!w.document) {
                    throw new Error("flowchart requires a window with a document");
                }
                return factory(w);
            };
    } else {
        factory(global);
    }
})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
    "use strict";

    let POINT_NAME = "point";
    let POINT_COLOR = "#f6f7ff";
    let POINT_ACTIVITE_COLOR = "#d9230f";
    let DEBUG = false;
    let idCounter = function () {
        return uuidv4().replace(/-/g, "").toUpperCase();
    };

    let NODE = {
        name: "group", // konva name
        type: "",
        color: "#f6f7ff", // konva fill
        borderColor: "#03689a", // konva stroke
        text: "",
        width: 200,
        height: 35,
        padding: 10,
        fontSize: 16,
        align: "center",
        fill: 'black',
        draggable: true,
        onTextChange: function(event, node, newText, oldText, callback = (text)=>{}){
            if (DEBUG) console.log(`onTextChange `, arguments)
        },
        onClick: function (event, node) {
            if (DEBUG) console.log(`onClick `, arguments)
        },
        onDblclick: function (event, node) {
            if (DEBUG) console.log(`onDblclick `, arguments)
        },
        onDragstart: function (event, node) {
            if (DEBUG) console.log(`onDragstart `, arguments)
        },
        onDragmove: function (event, node) {
            if (DEBUG) console.log(`onDragmove `, arguments)
        },
        onDragend: function (event, node) {
            if (DEBUG) console.log(`onDragend `, arguments)
        },
        onSelected: function (event, node) {
            if (DEBUG) console.log(`onSelected `, arguments)
        },
    };

    let ARROW_TEXT = {
        fontSize: 14,
        align: "center",
        fill: 'black',
    };

    let DEFAULT_NODE_TYPE_LIST = [{
        "type": "start",
        "text": "模型输入",
        color: "#ebf9dc"
    },
        {
            "type": "service",
            "text": "内部服务"
        },
        {
            "type": "end",
            "text": "模型输出",
            color: "#701321c2"
        },
    ];

    let DEFAULT_OPTIONS = {
        container: "",
        name: "container",
        width: window.innerWidth,
        height: window.innerHeight,
        hidePanel: false,
        disableLinked: false,
        showLinkedText: false,
        onArrowClick: function (event, node) {
            if (DEBUG) console.log(`onArrowClick `, arguments)
        },
        onArrowDblclick: function (event, node) {
            if (DEBUG) console.log(`onArrowDblclick `, arguments)
        },
        onDeleteKeyup: function(event, nodes) {
            if (DEBUG) console.log(`onDeleteKeyup `, arguments)
        },
        panel: {
            name: "panel",
            draggable: false,
            width: 250,
            height: 300,
            nodes: []
        }, // 面板节点
    };

    let DEFAULT_POINT_CONFIG = {
        name: POINT_NAME,
        radius: 7,
        fill: POINT_COLOR,
        stroke: '#03689a',
        strokeWidth: 2,
    };

    let Flowchart = function (options) {
        this._options = $.extend(true, {}, DEFAULT_OPTIONS, options);
        if (!this._options.panel.nodes || this._options.panel.nodes.length == 0) {
            this._options.panel.nodes = DEFAULT_NODE_TYPE_LIST.map(function (val) {
                return $.extend({}, NODE, val)
            });
        } else {
            this._options.panel.nodes = this._options.panel.nodes.map(function (val) {
                return $.extend({}, NODE, val)
            });
        }

        this._readonly = false;

        this.init();
    };

    Flowchart.prototype.readonly = function (flag) {
        if (typeof flag !== "undefined") {
            this._readonly = flag;
        } else {
            return this._readonly;
        }
    };

    // 开始初始化流程
    Flowchart.prototype.init = function () {
        this.container = {};
        let container = this.container;
        container.stage = new Konva.Stage({
            container: this._options.container,
            width: this._options.width,
            height: this._options.height
        });
        container.layer = new Konva.Layer();

        let paddingRight = 50;
        if (!this._options.hidePanel) {
            container.panel = new Konva.Rect({
                x: this._options.width - this._options.panel.width - paddingRight,
                y: 30,
                width: this._options.panel.width,
                height: this._options.panel.height,
                fill: '#ffffff',
                stroke: '#ffe187',
                strokeWidth: 2
            });
            container.layer.add(container.panel);
        } else {
            let con = container.stage.container();
            con.addEventListener('dragover', function (e) {
                e.preventDefault(); // !important
            });

            con.addEventListener('drop',  (e)=>{
                e.preventDefault();
                // now we need to find pointer position
                // we can't use stage.getPointerPosition() here, because that event
                // is not registered by Konva.Stage
                // we can register it manually:
                container.stage.setPointersPositions(e);
                let node = this.panelNodesMap[window.$$FLOWCHART_NODE_DATA.type];
                let el = new Konva.Rect({
                    name: "el",
                    width: node.width,
                    height: node.height,
                    fill: node.color || '#ffffff',
                    stroke: node.borderColor,
                    strokeWidth: 2
                });
                let text = new Konva.Text({
                    name: "nodeText",
                    width: node.width,
                    height: node.height,
                    text: node.text,
                    padding: node.padding || 10,
                    fontSize: node.fontSize,
                    align: node.align,
                    fill: node.fill
                });
                let pointer = container.stage.getPointerPosition();
                let group = new Konva.Group({
                    x: pointer.x,
                    y: pointer.y,
                    draggable: node.draggable,
                    name: node.name
                });
                group.id(idCounter()); // 设置id
                // 添加到布局中
                group.add(el);
                group.add(text);
                container.layer.add(group);
                // 保存节点参数到konva对象中
                group.$data = $.extend({}, node);
                // 初始化事件监听
                this.initGroupEvent(group);
                // refresh
                container.layer.draw();
            });
        }

        // 添加panel 面板
        let startX = this._options.width - this._options.panel.width - paddingRight / 2;
        let startY = 30 + 20;

        this.panelNodesMap = {};
        this._options.panel.nodes.forEach((node) => {
            this.panelNodesMap[node.type] = node;
            let el = new Konva.Rect({
                name: "el",
                width: node.width,
                height: node.height,
                fill: node.color || '#ffffff',
                stroke: node.borderColor,
                strokeWidth: 2
            });
            let text = new Konva.Text({
                name: "nodeText",
                width: node.width,
                height: node.height,
                text: node.text,
                padding: node.padding || 10,
                fontSize: node.fontSize,
                align: node.align,
                fill: node.fill
            });
            let group = new Konva.Group({
                x: startX,
                y: startY,
                draggable: node.draggable,
                name: node.name
            });
            // 初始化事件监听
            group.on("dragstart", this.dragstartHandler.bind(this));
            group.on("dragend", evt => container.stage.container().style.cursor = 'default');
            // 保存节点参数到konva对象中
            group.$data = $.extend({}, node);
            // 添加到布局中
            group.add(el);
            group.add(text);
            if (!this._options.hidePanel) {
                container.layer.add(group);
            }
            startY = startY + node.height + 20;
        });

        container.tr = new Konva.Transformer({
            resizeEnabled: false,
            rotateEnabled: false,
            borderStrokeWidth: 4
        });
        container.layer.add(container.tr);
        container.stage.add(container.layer);
        container.stage.on("mousemove", this.updateCombineArrow.bind(this));
        container.stage.on('click tap dragstart', this.transformerClickHandler.bind(this));
        container.stage.on('contextmenu', this.contextmenuEventHandler.bind(this));
        $(window).on("click", this.contextmenuHideHandler.bind(this));
        // 添加取消事件处理
        $(window).keyup((e) => {
            if (e.keyCode === 27) {
                this.cancelCombineArrow.apply(this);
            } else if (e.keyCode === 46) {
                let nodes = container.tr.nodes();
                if (!nodes) {
                    nodes = [];
                }
                if (this.container.selectedArrow) {
                    nodes.push(this.container.selectedArrow);
                } else {
                    let combineDone = container.combineDone;
                    let arrowsRelates = [];
                    if (combineDone) {
                        nodes.forEach( group => {
                            if (group.getClassName() == "Group") {
                                combineDone.forEach(combine => {
                                    if (combine[0] === group || combine[2] === group) {
                                        arrowsRelates.push(combine[4]);
                                    }
                                })
                            }
                        });
                        if(arrowsRelates.length > 0) {
                            nodes = nodes.concat(arrowsRelates);
                        }

                    }
                }
                this._options.onDeleteKeyup.apply(this, [e, nodes, this.removeNodesCallback.bind(this)])
            } else {
                return;
            }
            e.preventDefault();
            container.layer.draw();
        });


    };

    Flowchart.prototype.removeNodesCallback = function(nodes) {
        if (nodes && nodes.length > 0) {
            let container = this.container;
            let combineDone = container.combineDone;
            nodes.forEach((el)=> {
                if (el.getClassName() == "Group") {
                    let newCombineDone = [];
                    for (let i=0; i<combineDone.length; i++) {
                        let combine = combineDone[i];
                        if (combine[0] === el || combine[2] === el) {
                            combine[4].remove();
                        } else {
                            newCombineDone.push(combine);
                        }
                    }
                    container.combineDone = newCombineDone;
                } else if (el.getClassName() == "Arrow"){
                    let newCombineDone = [];
                    for (let i=0; i<combineDone.length; i++) {
                        let combine = combineDone[i];
                        if (combine[4] === el ) {
                            //combine[4].remove();
                            // 删除ArrorText
                            if (combine[5]) {
                                combine[5].remove();
                            }
                        } else {
                            newCombineDone.push(combine);
                        }
                    }
                    container.combineDone = newCombineDone;
                }
                el.remove();
            });
            container.tr.nodes([]);
            container.selectedArrow = null;
            this.container.layer.draw();
        }
    };

    Flowchart.prototype.transformerClickHandler = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let tr = container.tr;
        tr.zIndex(3);

        if (evt.target === stage) {
            let nodes = tr.nodes();
            if (!nodes) {
                nodes = [];
            }
            nodes.forEach((el)=>{
                this.offTextClickEvent(el);
            });
            tr.nodes([]);
            if (container.selectedArrow) {
                container.selectedArrow.fill('#808080');
                container.selectedArrow.stroke('#808080');
                container.selectedArrow = null;
            }
            layer.draw();
            return;
        }
        let target = evt.target;
        if (target.getClassName() == "Arrow") {
            // call arrow onSelected event handler
            container.selectedArrow = target;
            target.fill('#00a1ff');
            target.stroke('#00a1ff');
            layer.draw();
            return;
        }

        if (target.hasName(POINT_NAME)) {
            return;
        }

        // target on group
        while (target.getClassName() != "Group") {
            target = target.getParent();
            if (!target) {
                return;
            }
        }
        tr.nodes([target]);
        // call node onSelected event
        if (target.$data.onSelected) {
            target.$data.onSelected(evt, target.$data, target, this);
            this.bindTextClickEvent(target);
        }
        // tr.moveToBottom();
        layer.draw();
    };

    Flowchart.prototype.dragstartHandler = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let group = evt.target;
        // 鼠标样式
        stage.container().style.cursor = 'move';
        if (!this.readonly()) {
            // 克隆一个新对象 停留在原处，给下一次拖拽新增
            let newGroup = group.clone();
            newGroup.$data = $.extend({}, group.$data);
            layer.add(newGroup);

            group.id(idCounter()); // 设置id
            // 初始化事件监听
            this.initGroupEvent(group);
            // refresh
            layer.draw();
            return true;
        } else {
            group.draggable(false);
            return false;
        }
    };

    Flowchart.prototype.offTextClickEvent = function(group) {
        let self = this;
        while (group.getClassName() != "Group") {
            group = group.getParent();
            if (!group) {
                return;
            }
        }
        let nodeTextObj = group.findOne('.nodeText');
        nodeTextObj.off('click');
    };

    Flowchart.prototype.bindTextClickEvent = function(group) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let node = group.$data;

        let self = this;
        // handle text event
        let nodeTextObj = group.findOne('.nodeText');
        nodeTextObj.on("click", (e)=>{
            if (node.textModifyFlag || node.timerId) {
                return;
            }
            node.timerId = setTimeout(function(){
                nodeTextObj.hide();
                layer.draw();
                node.textModifyFlag = true;
                let oldText = nodeTextObj.text();

                let textPosition = nodeTextObj.absolutePosition();
                let stageBox = stage.container().getBoundingClientRect();
                let areaPosition = {
                    x: stageBox.left + textPosition.x,
                    y: stageBox.top + textPosition.y,
                };
                let textarea = document.createElement('input');
                document.body.appendChild(textarea);
                textarea.id = node.id;
                textarea.value = nodeTextObj.text();
                textarea.style.position = 'absolute';
                textarea.style.top = areaPosition.y + 'px';
                textarea.style.left = areaPosition.x + 'px';
                textarea.style.width = nodeTextObj.width() - nodeTextObj.padding() * 2 + 'px';
                textarea.style.height = node.height + 'px';
                textarea.style.fontSize = nodeTextObj.fontSize() + 'px';
                textarea.style.border = 'none';
                textarea.style.padding = '0px';
                textarea.style.margin = '0px';
                textarea.style.overflow = 'hidden';
                textarea.style.background = 'none';
                textarea.style.outline = 'none';
                textarea.style.resize = 'none';
                textarea.style.lineHeight = nodeTextObj.lineHeight();
                textarea.style.fontFamily = nodeTextObj.fontFamily();
                textarea.style.transformOrigin = 'left top';
                textarea.style.textAlign = nodeTextObj.align();
                textarea.style.color = nodeTextObj.fill();
                let rotation = nodeTextObj.rotation();
                let transform = '';
                if (rotation) {
                    transform += 'rotateZ(' + rotation + 'deg)';
                }

                let px = 0;
                // also we need to slightly move textarea on firefox
                // because it jumps a bit
                let isFirefox =
                    navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
                if (isFirefox) {
                    px += 2 + Math.round(nodeTextObj.fontSize() / 20);
                }
                transform += 'translateY(-' + px + 'px)';

                textarea.style.transform = transform;

                textarea.focus();

                function invokeOnTextChange(e, group, newText) {
                    node.timerId = null;
                    node.textModifyFlag = false;
                    node.onTextChange(e, group, newText, oldText, (text)=>{
                        nodeTextObj.text(text);
                        node.text = text;
                        layer.draw();
                    })
                }

                function removeTextarea() {
                    textarea.parentNode.removeChild(textarea);
                    window.removeEventListener('click', handleOutsideClick);
                    nodeTextObj.show();
                    layer.draw();
                }

                function setTextareaWidth(newWidth) {
                    if (!newWidth) {
                        // set width for placeholder
                        newWidth = nodeTextObj.placeholder.length * nodeTextObj.fontSize();
                    }
                    // some extra fixes on different browsers
                    let isSafari = /^((?!chrome|android).)*safari/i.test(
                        navigator.userAgent
                    );
                    let isFirefox =
                        navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
                    if (isSafari || isFirefox) {
                        newWidth = Math.ceil(newWidth);
                    }

                    let isEdge =
                        document.documentMode || /Edge/.test(navigator.userAgent);
                    if (isEdge) {
                        newWidth += 1;
                    }
                    textarea.style.width = newWidth + 'px';
                }

                textarea.addEventListener('keydown', function (e) {
                    // hide on enter
                    // but don't hide on shift + enter
                    if (e.keyCode === 13 && !e.shiftKey) {
                        invokeOnTextChange(e, group, textarea.value);
                        removeTextarea();
                    }
                    // on esc do not set value back to node
                    if (e.keyCode === 27) {
                        removeTextarea();
                    }
                });

                textarea.addEventListener('keydown', function (e) {
                    let scale = nodeTextObj.getAbsoluteScale().x;
                    setTextareaWidth(nodeTextObj.width() * scale);
                    //textarea.style.height = 'auto';
                    //textarea.style.height = textarea.scrollHeight + nodeTextObj.fontSize() + 'px';
                });

                function handleOutsideClick(e) {
                    if (e.target !== textarea) {
                        invokeOnTextChange(e, group, textarea.value);
                        removeTextarea();
                    }
                }
                setTimeout(() => {
                    window.addEventListener('click', handleOutsideClick);
                });
            }, 500);
        });
    };

    Flowchart.prototype.initGroupEvent = function (group) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let node = group.$data;

        group.off("dragstart");
        group.on("click", (event) => {
            //event.cancelBubble = true;
            node.onClick(event, group, node)
        });
        group.on("dblclick", (event) => {
            event.cancelBubble = true;
            if (node.timerId) {
                clearTimeout(node.timerId);
                node.timerId = null;
                node.textModifyFlag = false;
            }
            node.onDblclick(event, group, node)
        });
        group.on("dragstart", (event) => {
            stage.container().style.cursor = 'move';
            node.onDragstart(event, group, node)
        });
        group.on("dragmove", (event) => {
            this.updateCombineDoneByDrag.call(this, event, group)
        });
        group.on("dragmove", (event) => {
            node.onDragmove(event, group, node)
        });
        group.on("dragend", (event) => {
            node.onDragend(event, group, node)
        });

        let elH = group.$data.height;
        let elW = group.$data.width;

        let self = this;
        if (!this._options.disableLinked) {
            group.on("mouseover", function (e) {
                stage.container().style.cursor = 'move';
                let mp = stage.getPointerPosition();
                let tp = e.target.absolutePosition();
                let h = elH;
                let w = elW;
                // 判断是在上还是在下

                let points = group.find(".point");
                if (!points || points.toArray().length == 0) {
                    self.createPointsForGroup(group);
                } else {
                    points.each(p => p.show());
                }
                // refresh
                layer.draw();
            });
            group.on("mouseout", function (evt) {
                stage.container().style.cursor = 'default';
                self.refreshGroupPoints(group, false);
                // refresh
                layer.draw();
            });
        } else {
            self.refreshGroupPoints(group, false);
        }
    };

    Flowchart.prototype.getGroupActivePoint = function (group) {
        let active;
        let points = group.find(".point");
        for (let i = 0; i < points.length; i++) {
            let p = points[i];
            if (p.activeFlag) {
                active = p;
                break;
            }
        }
        return active;
    }

    // 拖拽group时， 将连接也联动拖拽
    Flowchart.prototype.updateCombineDoneByDrag = function (evt, group) {
        let activePoint = this.getGroupActivePoint(group);
        let container = this.container;
        let combineDone = container.combineDone;

        if (combineDone) {
            combineDone.forEach(combine => {
                if (combine[0] === group || combine[2] === group) {
                    let headGroup = combine[0];
                    let tailGroup = combine[2];
                    // 原点
                    let originX = headGroup.absolutePosition().x + headGroup.width() / 2;
                    let originY = headGroup.absolutePosition().y + headGroup.height() / 2;
                    // 对点
                    let targetX = tailGroup.absolutePosition().x + tailGroup.width() / 2;
                    let targetY = tailGroup.absolutePosition().y + tailGroup.height() / 2;

                    let arrow = combine[4];
                    let sin45 = Math.sin(Math.PI / 180 * 30);

                    let x = targetX - originX;
                    let y = targetY - originY;
                    let r = Math.sqrt(x * x + y * y);
                    /**
                     *        |
                     *    c   |   b
                     *        |
                     * ---------------
                     *        |
                     *    d   |   a
                     *        |
                     *
                     */
                    let ps = arrow.points();
                    let position, tposition;
                    let sin = Math.abs(y / r);
                    if (x > 0 && y > 0) {
                        // position a
                        position = sin > sin45 ? "b" : "r";
                        tposition = sin > sin45 ? "t" : "l";
                    } else if (x > 0 && y < 0) {
                        // position b
                        position = sin > sin45 ? "t" : "r";
                        tposition = sin > sin45 ? "b" : "l";
                    } else if (x < 0 && y < 0) {
                        // position c
                        position = sin > sin45 ? "t" : "l";
                        tposition = sin > sin45 ? "b" : "r";
                    } else if (x < 0 && y > 0) {
                        // position d
                        position = sin > sin45 ? "b" : "l";
                        tposition = sin > sin45 ? "t" : "r";
                    }
                    if (position) {
                        let opoints = this.createPointsForGroup(headGroup);
                        let oCircle, tCircle;
                        for (let i = 0; i < opoints.length; i++) {
                            if (opoints[i]._positionType == position) {
                                oCircle = opoints[i];
                                break;
                            }
                        }
                        combine[1] = oCircle;
                        let tpoints = this.createPointsForGroup(tailGroup);
                        for (let i = 0; i < tpoints.length; i++) {
                            if (tpoints[i]._positionType == tposition) {
                                tCircle = tpoints[i];
                                break;
                            }
                        }
                        combine[3] = tCircle;
                        let offset = this.calcOffset(oCircle.absolutePosition().x, oCircle.absolutePosition().y,
                            tCircle.absolutePosition().x, tCircle.absolutePosition().y, 7);
                        ps[0] = oCircle.absolutePosition().x + offset[0];
                        ps[1] = oCircle.absolutePosition().y + offset[1];
                        ps[2] = tCircle.absolutePosition().x - offset[0];
                        ps[3] = tCircle.absolutePosition().y - offset[1];
                        //console.log(`ps ${ps}, origin ${position}, target ${tposition} sin ${sin} sin45 ${sin45} asin ${Math.asin(sin)/(Math.PI / 180)}`);
                    }
                    arrow.points(ps);
                    this.drawArrorwText(combine);
                }
            });
        }
    }

    // 计算偏移 coordinate
    Flowchart.prototype.calcOffset = function (ox, oy, tx, ty, offset) {
        let x = tx - ox;
        let y = ty - oy;
        let r = Math.sqrt(x * x + y * y);
        let sin = y / r;
        let cos = x / r;
        let r1 = r - offset;
        let x1 = x - cos * r1;
        let y1 = y - sin * r1;
        return [x1, y1];
    }

    // 刷新绘制过程的箭头 随鼠标动
    Flowchart.prototype.updateCombineArrow = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let combine = container.combine;
        if (combine) {
            let mp = stage.getPointerPosition();
            let tp = combine[1].absolutePosition();
            let arrowps = combine[4].points();
            // 原点
            let originX = arrowps[0];
            let originY = arrowps[1];
            let offset = this.calcOffset(tp.x, tp.y, mp.x, mp.y, 7);
            arrowps[0] = tp.x + offset[0];
            arrowps[1] = tp.y + offset[1];
            arrowps[2] = mp.x - offset[0];
            arrowps[3] = mp.y - offset[1];
            combine[4].points(arrowps);
            this.drawArrorwText(combine);
            layer.draw();
        }
    }
    // 取消取连接 e.keyCode === 27 ESC or dblclick
    Flowchart.prototype.cancelCombineArrow = function (evt) {
        let container = this.container;
        let combine = container.combine;
        if (combine && combine[2] == null) {
            this.togglePointActive(combine[1], false);
            let arrow = combine[4];
            arrow.hide();
            arrow.destroy();
            container.combine = null;
        }
    }

    // 更新点的激活状态
    Flowchart.prototype.togglePointActive = function (point, activeFlag) {
        if (point) {
            if (typeof activeFlag === "undefined") {
                point.activeFlag = !point.activeFlag;
            } else {
                point.activeFlag = activeFlag;
            }
            point.fill(point.activeFlag ? POINT_ACTIVITE_COLOR : POINT_COLOR);
        }
        return point;
    }

    // 更新点的
    Flowchart.prototype.refreshGroupPoints = function (group, show) {
        if (group.children) {
            let points = group.find(".point");
            let hasActive = false;
            points.each(p => {
                if (p.activeFlag) {
                    hasActive = true;
                }
            });
            if (points && !hasActive) points.each(p => p[show ? "show" : "hide"]());
        }
    }

    // point 点击连线
    Flowchart.prototype.pointClick = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let point = evt.target;
        if (point.hasName(POINT_NAME)) {
            let group = point.getParent();
            this.togglePointActive(point);
            // 清除其它的激活状态
            let points = group.find(".point");
            if (points) {
                points.each(p => {
                    if (!(p === point)) {
                        this.togglePointActive(p, false);
                    }
                });
            }
            // 连接处理部分
            if (point.activeFlag) {

                let mp = stage.getPointerPosition();
                let tp = point.absolutePosition();
                // 判断是否当前有头部对象 没有则初始化 初始后直接返回 等待用户选择下一个点
                if (!container.combine) {
                    // 记录当前连接的对象   头    头的点    尾     尾的点  箭头
                    container.combine = [group, point, null, null, null];
                    let offset = this.calcOffset(tp.x, tp.y, mp.x, mp.y, 7);
                    // 绘制连线
                    let arrow = new Konva.Arrow({
                        x: 0,
                        y: 0,
                        points: [tp.x, tp.y, mp.x - offset[0], mp.y - offset[1]],
                        pointerLength: 15,
                        pointerWidth: 15,
                        fill: '#808080',
                        stroke: '#808080',
                        id: idCounter(),
                        strokeWidth: 3
                    });
                    container.combine[4] = arrow;
                    // 判断初始化连接完成的集合
                    if (!container.combineDone) {
                        container.combineDone = [];
                    }
                    layer.add(arrow);
                    layer.draw();
                    return;
                }

                // 用户选择了下一个点， 进行两点连线
                let combine = container.combine;
                if (group === combine[0]) {
                    // 不能自己连自己
                    return;
                }
                container.combine = null; // 释放当前连接  给下一次让位

                combine[2] = group;
                combine[3] = point;
                // 推入完成集合中
                container.combineDone.push(combine);
                let arrowps = combine[4].points();
                let offset = this.calcOffset(arrowps[0], arrowps[1], tp.x, tp.y, 7);
                arrowps[2] = tp.x - offset[0];
                arrowps[3] = tp.y - offset[1];
                this.togglePointActive(combine[1], false);
                this.togglePointActive(combine[3], false);
                this.refreshGroupPoints(combine[0], false);
                // 绑定事件
                combine[4].on("click tap", (event) => {
                    this._options.onArrowClick(event, combine[0].$data, combine[2].$data);
                });
                combine[4].on("dblclick dbltap", (event) => {
                    this._options.onArrowDblclick(event, combine[0].$data, combine[2].$data);
                });
                combine[4].id(idCounter()); // 设置id
                layer.draw();
                return;
            }

            layer.draw();
        }
    };

    Flowchart.prototype.createPointsForGroup = function (group) {
        let self = this;
        let points = group.find(".point");
        let h = group.$data.height;
        let w = group.$data.width;
        if (!points || points.toArray().length == 0) {
            let t = new Konva.Circle($.extend({
                id: idCounter(),
                x: w / 2,
                y: 0
            }, DEFAULT_POINT_CONFIG));
            t._positionType = "t";
            let b = new Konva.Circle($.extend({
                id: idCounter(),
                x: w / 2,
                y: h
            }, DEFAULT_POINT_CONFIG));
            b._positionType = "b";
            let l = new Konva.Circle($.extend({
                id: idCounter(),
                x: 0,
                y: h / 2
            }, DEFAULT_POINT_CONFIG));
            l._positionType = "l";
            let r = new Konva.Circle($.extend({
                id: idCounter(),
                x: w,
                y: h / 2
            }, DEFAULT_POINT_CONFIG));
            r._positionType = "r";
            //point.on("click tap", lineClickHandler);
            let ps = [t, b, l, r];
            group.add.apply(group, ps);
            ps.forEach(p => p.on("click tap", self.pointClick.bind(self)));
            points = group.find(".point");
        }
        return points;
    }

    Flowchart.prototype.drawArrorwText = function(combine) {
        if (!this._options.showLinkedText) {
            return;
        }
        if (combine[4] && !combine[4].text) {
            return ;
        }

        let points = combine[4].points();
        if (combine[5]) {
            // text 存在 更新位置与text
            let text = combine[5];
            text.x((points[2] + 2 * points[0]) / 3 + 10),
                text.y((points[3] + points[1]) / 2 - 20),
                text.text(combine[4].text);
        } else {
            // text 不存在 创建加入画布
            let text = new Konva.Text({
                x: (points[2] + 2 * points[0]) / 3 + 10,
                y: (points[3] + points[1]) / 2 - 20,
                text: combine[4].text,
                fontSize: ARROW_TEXT.fontSize,
                align: ARROW_TEXT.align,
                fill: ARROW_TEXT.fill,
            });
            combine[5] = text;
            this.container.layer.add(text);
        }
    };

    Flowchart.prototype.contextmenuEventHandler = function(e) {
        let container = this.container;
        let stage = container.stage;
        if (e.target === stage) {
            // if we are on empty place of the stage we will do nothing
            return;
        }

        let target = e.target;
        // target on group
        while (target.getClassName() != "Group") {
            target = target.getParent();
            if (!target) {
                return;
            }
        }
        // 未配置右键菜单直接返回
        if (!(target.$data && target.$data.contextmenu && target.$data.contextmenu.length > 0)) {
            return;
        }
        // prevent default behavior
        e.evt.preventDefault();
        this.currentShape = target;
        let nodeInfo = target.$data;
        let menuCtx = null;
        if (!document.getElementById(`contextmenu-${nodeInfo.id}`)) {
            let contextmenu = target.$data.contextmenu ;
            menuCtx = $(`<div id="contextmenu-${nodeInfo.id}" class="node-contextmenu"></div>`);
            menuCtx.css("position", "absolute");
            menuCtx.css("width", "60px");
            menuCtx.css("background-color", "white");
            menuCtx.css("box-shadow", "0 0 5px grey");
            menuCtx.css("border-radius", "3px");
            contextmenu.forEach((menu, idx)=>{
                let btn = $(`<button class="node-contextnemu-btn">${menu.name}</button>`);
                btn.css("width", "100%");
                btn.css("background-color", "white");
                btn.css("border", "none");
                btn.css("margin", "0");
                btn.css("padding", "10px");
                btn.on("click", (event)=>{
                    if (menu.click) {
                        menu.click.call(this, event, target, nodeInfo);
                    }
                });
                btn.hover((event)=>{
                    $(event.target).css("background-color", "lightgray");
                }, (event)=>{
                    $(event.target).css("background-color", "white");
                });
                menuCtx.append(btn);
            });
            $('body').append(menuCtx);
        } else {
            menuCtx = $(`#contextmenu-${nodeInfo.id}`);
        }
        menuCtx.css("display", 'initial');
        let containerRect = stage.container().getBoundingClientRect();
        menuCtx.css("top", containerRect.top + stage.getPointerPosition().y + 4 + 'px');
        menuCtx.css("left", containerRect.left + stage.getPointerPosition().x + 4 + 'px');
    };

    Flowchart.prototype.contextmenuHideHandler = function(e) {
        $('.node-contextmenu').hide();
    };

    // 加载流程图
    Flowchart.prototype.load = function (data = {}) {
        let flowNodes = data.nodes || [];
        let links = data.links || [];
        let container = this.container;
        let layer = container.layer;
        // 判断初始化连接完成的集合
        if (!container.combineDone) {
            container.combineDone = [];
        }

        let nodeMap = this.panelNodesMap;

        flowNodes.forEach(node => {
            let obj = Konva.Node.create(node);
            let $$data = nodeMap[node.$data.type];
            obj.$data = $.extend({}, $$data, node.$data);
            let points = obj.find(".point");
            points.forEach(child => {
                for (let i = 0; i < node.children.length; i++) {
                    let point = node.children[i];
                    if (child.id() == point.attrs.id && point.className === "Circle") {
                        child._positionType = point._positionType;
                        break;
                    }
                }
                // 点的事件初始化
                child.on("click tap", this.pointClick.bind(this))
            });
            // 初始化事件监听
            this.initGroupEvent(obj);
            if (!this._options.disableLinked) {
                this.createPointsForGroup(obj);
            }
            layer.add(obj);
        });
        links.forEach(link => {
            let arrow = Konva.Node.create(link);
            arrow.text = link.text;
            let connect = link.connect;
            if (!connect || connect.length == 0) {
                connect = [link.startNode, null, link.endNode, null];
            }
            let combine = [];
            // 找头找尾找到点
            layer.children.forEach(el => {
                if (el.id() === connect[0]) {
                    combine[0] = el;

                } else if (el.id() === connect[2]) {
                    combine[2] = el;
                }
            });
            combine[4] = arrow;
            layer.add(arrow);
            this.drawArrorwText(combine);
            container.combineDone.push(combine);
            // 初始化事件监听
            arrow.on("click tap", (event) => {
                this._options.onArrowClick(event, combine[0].$data, combine[2].$data);
            });
            arrow.on("dblclick dbltap", (event) => {
                this._options.onArrowDblclick(event, combine[0].$data, combine[2].$data);
            });
        });
        // 刷新画布
        layer.draw();
    };
    // 导出流程图
    Flowchart.prototype.export = function () {
        // 面板节点信息
        let nodeMap = this.panelNodesMap;
        let container = this.container;
        let layer = container.layer;

        let flowNodes = [],
            links;
        layer.children.forEach(child => {
            if (child.getClassName() == "Group" && child.$data && !!nodeMap[child.$data.type]) {
                let nodeObj = child.toObject();
                nodeObj.$data = child.$data;
                let points = child.find(".point");
                if (!this._options.disableLinked) {
                    if (!points || points.length == 0) {
                        return;
                    }
                }
                // 记录点的类型
                nodeObj.children.forEach(child => {
                    if (child.className === "Circle") {
                        for (let i = 0; i < points.length; i++) {
                            if (child.attrs.id == points[i].id()) {
                                child._positionType = points[i]._positionType;
                                break;
                            }
                        }
                    }
                });
                flowNodes.push(nodeObj);
            }
        });
        links = container.combineDone.map(link => {
            let linkObj = link[4].toObject();
            linkObj.startNode = link[0].id();
            linkObj.endNode = link[2].id();
            //linkObj.connect = [link[0].id(), link[1]._positionType, link[2].id(), link[3]._positionType];
            return linkObj
        });
        return {
            nodes: flowNodes,
            links: links
        };
    }

    Flowchart.prototype.factory = function (type, data) {
        if (type == "Node") {
            return {
                "attrs": {
                    "x": data.x,
                    "y": data.y,
                    "draggable": true,
                    "name": "group",
                    "id": data.id
                },
                "className": "Group",
                "children": [{
                    "attrs": {
                        "name": "el",
                        "width": 200,
                        "height": 35,
                        "fill": "#f6f7ff",
                        "stroke": "#03689a"
                    },
                    "className": "Rect"
                }, {
                    "attrs": {
                        "name": "nodeText",
                        "width": 200,
                        "height": 35,
                        "text": data.text,
                        "padding": 10,
                        "fontSize": 16,
                        "align": "center",
                        "fill": "black"
                    },
                    "className": "Text"
                }],
                "$data": $.extend({}, this.panelNodesMap[data.type], data)
            };
        } else if (type == "Link") {
            return {
                "attrs": {
                    "points": data.points,
                    "pointerLength": 15,
                    "pointerWidth": 15,
                    "fill": "#808080",
                    "stroke": "#808080",
                    "strokeWidth": 3,
                    "id": data.id
                },
                "className": "Arrow",
                "startNode": data.startNode,
                "endNode": data.endNode,
                "text": data.text,
            }
        }
    }

    Flowchart.prototype.refresh = function() {
        this.container.layer.draw();
    }

    if (typeof noGlobal === "undefined") {
        window.Flowchart = Flowchart;
    }

    return Flowchart;
})